package cn.keniykey.mc.viewlibrary.reflection;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;

import javax.annotation.Nullable;

public final class SearchReflectionMethod implements Cloneable{
	private String name,name_prefix,name_suffix; //方法完全名，方法名前部分，方法名后部分
//	private LinkedList<Class<?>[]> parameter;
	private LinkedList<Object> linked_parameter; //方法参数（包含泛型）
	private Class<?>[] turn; //返回内容
//	private boolean is_static,is_final,is_abstract,is_private,is_protected,is_public; //静态,终态,抽象,私有,半公开,公开
	private boolean is_static,is_final,is_abstract;
	
	private EReflectionPermission permission;
	
	private boolean check_static,check_final,check_abstract; //参与检查
	
	private final int methods_index;
	
	public SearchReflectionMethod(int index) {
		this.methods_index = index;
	}
	public SearchReflectionMethod() {
		this.methods_index = 0;
	}
	
	public SearchReflectionMethod setName(@Nullable String name1) {
		this.name = name1;
		return this;
	}
	public SearchReflectionMethod setNamePrefix(@Nullable String prefix) {
		this.name_prefix = prefix;
		return this;
	}
	public SearchReflectionMethod setNameSuffix(@Nullable String suffix) {
		this.name_suffix = suffix;
		return this;
	}
	public SearchReflectionMethod addParameter(Class<?>...classes) {
		if(this.linked_parameter == null) this.linked_parameter = new LinkedList<>();
		for(Class<?> clazz : classes) this.linked_parameter.add(clazz);
		return this;
	}
	public SearchReflectionMethod addParameter(ReflectionClass...classes) {
		if(this.linked_parameter == null) this.linked_parameter = new LinkedList<>();
		for(final ReflectionClass reclazz : classes) this.linked_parameter.add(reclazz);
		return this;
	}
	public SearchReflectionMethod addParameter(ReflectionGenericClass...classes) {
		if(this.linked_parameter == null) this.linked_parameter = new LinkedList<>();
		for(final ReflectionGenericClass reclazz : classes) this.linked_parameter.add(reclazz);
		return this;
	}
	public SearchReflectionMethod nonParameter() {
		if(this.linked_parameter == null) {
			this.linked_parameter = new LinkedList<>();
		}else {
			this.linked_parameter.clear();
		}
		return this;
	}
	public SearchReflectionMethod setReturn(Class<?>... classes) {
		this.turn = classes;
		return this;
	}
	public SearchReflectionMethod setStatic(boolean state) {
		this.is_static = state;
		if(state) this.check_abstract = this.is_abstract = false;
		this.check_static = true;
		return this;
	}
	public SearchReflectionMethod setFinal(boolean state) {
		this.is_final = state;
		this.check_final = true;
		return this;
	}
	public SearchReflectionMethod setAbstract(boolean state) {
		this.is_abstract = state;
		if(state) this.check_static = this.is_static = false;
		this.check_abstract = true;
		return this;
	}
	public SearchReflectionMethod setPermission(@Nullable EReflectionPermission meper) {
		this.permission = meper;
		return this;
	}
	ArrayList<Method> findMethods(final Class<?> clazz) {
		final HashSet<Method> hashset_methods = new HashSet<>();
		Collections.addAll(hashset_methods, clazz.getMethods());
		Collections.addAll(hashset_methods, clazz.getDeclaredMethods());
		final LinkedList<Method> methods = new LinkedList<>(hashset_methods);
		hashset_methods.clear();
		final Iterator<Method> it = methods.iterator();
		methods_while : while(it.hasNext()) {
			final Method method = it.next();
			if(this.name != null && !method.getName().equals(this.name)) {
				it.remove();
				continue;
			}
			if(this.name_prefix != null && !method.getName().startsWith(this.name_prefix)) {
				it.remove();
				continue;
			}
			if(this.name_suffix != null && !method.getName().endsWith(this.name_suffix)) {
				it.remove();
				continue;
			}
			final int modifiers = method.getModifiers();
			if(this.permission != null && !this.permission.isModifiers(modifiers)) {
				it.remove();
				continue;
			}
			if(this.check_abstract && Modifier.isAbstract(modifiers) != this.is_abstract) {
				it.remove();
				continue;
			}
        	if(this.check_final && Modifier.isFinal(modifiers) != this.is_final) {
        		it.remove();
        		continue;
        	}
        	if(this.check_static && Modifier.isStatic(modifiers) != this.is_static) {
        		it.remove();
        		continue;
        	}
			if(this.turn != null) {
				final Object turnobj = ReflectionGenericClass.getParameterClasses(method.getGenericReturnType());
				if(turnobj instanceof Class) {
					if(this.turn.length > 0  && !turnobj.equals(this.turn[0])) {
						it.remove();
						continue;
					}else if(this.turn.length == 0 && !turnobj.equals(Void.TYPE)) {
						it.remove();
						continue;
					}
				}else if(turnobj instanceof LinkedList) {
					final LinkedList<?> linkeds = (LinkedList<?>)turnobj;
					if(linkeds.size() != this.turn.length) {
						it.remove();
						continue;
					}
					int index = 0;
					for(final Object classobj : linkeds) {
						if(!classobj.equals(this.turn[index])) {
							it.remove();
							continue methods_while;
						}
					}
				}
			}
        	if(this.linked_parameter != null) {
        		final Type[] parametertypes = method.getGenericParameterTypes();
        		if(parametertypes.length != this.linked_parameter.size()) {
        			it.remove();
        			continue;
        		}
        		int index = 0;
        		for(final Object obj_clazz : this.linked_parameter) {
        			final Object obj = ReflectionGenericClass.getParameterClasses(parametertypes[index]);
        			index++;
        			if(obj instanceof Class) {
        				Class<?> coverclazz = null;
        				if(obj_clazz instanceof ReflectionGenericClass){
        					if(!((ReflectionGenericClass)obj_clazz).hasGenerics()) ((ReflectionGenericClass)obj_clazz).getClazz();
        				}else if(obj_clazz instanceof ReflectionClass) {
        					coverclazz = ((ReflectionClass)obj_clazz).getClazz();
        				}else if(obj_clazz instanceof Class) {
        					coverclazz = (Class<?>)obj_clazz;
        				}else {
        					coverclazz = null;
        				}
        				if(coverclazz == null || !coverclazz.equals(obj)) {
        					it.remove();
        					continue methods_while;
        				}
        			}else if(obj instanceof LinkedList) {
        				final LinkedList<?> linkeds = (LinkedList<?>)obj;
        				if(!(obj_clazz instanceof ReflectionGenericClass) || !((ReflectionGenericClass)obj_clazz).hasGenerics()) {
                			it.remove();
                			continue methods_while;
        				}
        				final ReflectionGenericClass rg_clazz = (ReflectionGenericClass)obj_clazz;
        				if(!linkeds.removeFirst().equals(rg_clazz.getClazz())) {
        					it.remove();
        					continue methods_while;
        				}
        				while(!rg_clazz.getGenerics().isEmpty()) {
        					final Class<?> rg_clazz_in = rg_clazz.getGenerics().remove(0);
        					if(ReflectionWildcard.class.equals(rg_clazz_in)) continue;
        					if(linkeds.isEmpty() || !linkeds.removeFirst().equals(rg_clazz_in)) {
            					it.remove();
            					continue methods_while;
        					}
        				}
        				if(!linkeds.isEmpty() || !rg_clazz.getGenerics().isEmpty()) {
        					it.remove();
        					continue methods_while;
        				}
        			}
        		}
        	}
		}
    	try {
    		return new ArrayList<>(methods);
    	}finally {
			methods.clear();
		}
	}
	Method findMethod(final Class<?> clazz) {
		final ArrayList<Method> methods = this.findMethods(clazz);
		if(methods.isEmpty()) throw new RuntimeException("can not find the Method(NoSuchMethodException)(找不到符合的Method方法)");
		try {
			if(this.methods_index >= methods.size()) throw new RuntimeException("the such method index out of found methods amount"
					+ "(IndexOutOfBoundsException)(需求方法索引位置超出目前搜到的上限): " + this.methods_index + "/" + methods.size());
			return methods.get(this.methods_index);
		}finally {
			methods.clear();
		}
	}
    @Override
    public String toString() {
        final StringBuilder b = new StringBuilder(this.getClass().getSimpleName() + "{");
        if (this.name != null) {
            b.append("[name:").append(this.name).append("]");
        }
        if (this.name_prefix != null) {
            b.append("[prefix:").append(this.name_prefix).append("]");
        }
        if (this.name_suffix != null) {
            b.append("[suffix:").append(this.name_suffix).append("]");
        }
        if(this.turn != null && this.turn.length > 0) {
        	b.append("[return:").append(Arrays.toString(this.turn)).append("]");
        }
        if(this.permission != null) {
        	b.append("[permission:").append(this.permission.toString()).append("]");
        }
        if (this.is_abstract) {
            b.append("[abstract:").append(this.is_abstract).append("]");
        }
        if (this.is_final) {
            b.append("[final:").append(this.is_final).append("]");
        }
        if (this.is_static) {
            b.append("[static:").append(this.is_static).append(",");
        }
        b.append("[index:").append(this.methods_index).append("]");
        b.append("}");
        return b.toString();
    }
	@Override
	public SearchReflectionMethod clone() {
		final SearchReflectionMethod search = new SearchReflectionMethod(this.methods_index);
		search.name = this.name;
		search.name_prefix = this.name_prefix;
		search.name_suffix = this.name_suffix;
		if(this.linked_parameter != null) {
			search.linked_parameter = new LinkedList<>();
			for(final Object obj_clazz : this.linked_parameter) {
//				final Class<?>[] newclasses = new Class[classes.length];
//				for(int index = 0; index < classes.length; index++) newclasses[index] = classes[index];
//				search.parameter.add(newclasses);
//				search.parameter.add(Arrays.copyOf(classes, classes.length));
				search.linked_parameter.add(obj_clazz);
			}
		}
		if(this.turn != null) {
//			search.turn = new Class[this.turn.length];
//			for(int index = 0; index < this.turn.length; index++) search.turn[index] = this.turn[index];
			search.turn = Arrays.copyOf(this.turn, this.turn.length);
		}
		search.is_static = this.is_static;
		search.is_final = this.is_final;
		search.is_abstract = this.is_abstract;
		search.permission = this.permission;
		
		search.check_static = this.check_static;
		search.check_final = this.check_final;
		search.check_abstract = this.check_abstract;
		return search;
	}
}